package references

import (
	"fmt"
	"hi-jvm/instructions/base"
	"hi-jvm/rtda"
	"hi-jvm/rtda/heap"
)

// Invoke instance method; dispatch based on class
type INVOKE_VIRTUAL struct{ base.Index16Instruction }

func (self *INVOKE_VIRTUAL) Execute(frame *rtda.Frame) {
	methodRef := getMethodRef(frame, self.Index)
	resolvedMethod := methodRef.ResolvedMethod()
	if resolvedMethod.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}

	ref := frame.OperandStack.GetRefFromTop(resolvedMethod.ArgSlotCount - 1)
	if ref == nil {
		// hack!
		if methodRef.Name == "println" {
			_println(frame.OperandStack, methodRef.Descriptor)
			return
		}

		panic("java.lang.NullPointerException")
	}
	currentClass := frame.Method.Class
	if resolvedMethod.IsProtected() {
		if	resolvedMethod.Class.IsSuperClassOf(currentClass) && // 是调用类的父类
			resolvedMethod.Class.GetPackageName() != currentClass.GetPackageName() && // 不在同一个包中
			ref.Class != currentClass && // 同一个类中
			!ref.Class.IsSubClassOf(currentClass) { // 不是调用类的子类（virtual的重写）
			panic("java.lang.IllegalAccessError")
		}
	}

	methodToBeInvoked := heap.LookupMethodInClass(ref.Class,
		methodRef.Name, methodRef.Descriptor)
	if methodToBeInvoked == nil || methodToBeInvoked.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}

	base.InvokeMethod(frame, methodToBeInvoked)
}

// hack!
func _println(stack *rtda.OperandStack, descriptor string) {
	switch descriptor {
	case "(Z)V":
		fmt.Printf("%v\n", stack.PopInt() != 0)
	case "(C)V":
		fmt.Printf("%c\n", stack.PopInt())
	case "(I)V", "(B)V", "(S)V":
		fmt.Printf("%v\n", stack.PopInt())
	case "(F)V":
		fmt.Printf("%v\n", stack.PopFloat())
	case "(J)V":
		fmt.Printf("%v\n", stack.PopLong())
	case "(D)V":
		fmt.Printf("%v\n", stack.PopDouble())
	default:
		panic("println: " + descriptor)
	}
	stack.PopRef()
}
